package storage

import (
	"bytes"
	"fmt"
	"github.com/boltdb/bolt"
	goettybuf "github.com/fagongzi/goetty"
	"github.com/pkg/errors"
	"io/ioutil"
	"os"
)

type boltDataEngine struct {
	db *bolt.DB
}

func newBoltDataEngine(db *bolt.DB) DataEngine {
	db.Update(func(tx *bolt.Tx) error {
		_, err := tx.CreateBucketIfNotExists([]byte("boltDataBucket"))
		if err != nil {
			return err
		}
		return nil
	})
	return &boltDataEngine{
		db: db,
	}
}

func (e *boltDataEngine) RangeDelete(start, end []byte) error {
	e.db.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("boltDataBucket"))
		c := b.Cursor()
		for k, _ := c.Seek(start); k != nil && bytes.Compare(k, end) <= 0; k, _ = c.Next() {
			err := b.Delete(k)
			if err != nil {
				return err
			}
		}
		return nil
	})
	return nil
}

func (e *boltDataEngine) GetTargetSizeKey(startKey []byte, endKey []byte, size uint64) (uint64, []byte, error) {
	return 0, nil, nil
}

func (e *boltDataEngine) ScanIndexInfo(start []byte, end []byte, skipEmpty bool, handler func(key, idxInfo []byte) error) (int, error) {
	return 1, errors.New("(*boltDataEngine).ScanIndexInfo is not implemented")
}

func (e *boltDataEngine) SetIndexInfo(key, idxInfo []byte) error {
	return errors.New("(*boltDataEngine).SetIndexInfo is not implemented")
}

func (e *boltDataEngine) GetIndexInfo(key []byte) (idxInfo []byte, err error) {
	err = errors.New("(*boltDataEngine).GetIndexInfo is not implemented")
	return
}

func (e *boltDataEngine) CreateSnapshot(path string, start, end []byte) error {
	err := os.MkdirAll(path, os.ModeDir)
	if err != nil {
		return nil
	}

	f, err := os.Create(fmt.Sprintf("%s/data.sst", path))
	if err != nil {
		return err
	}
	defer f.Close()

	return e.db.View(func(tx *bolt.Tx) error {
		c := tx.Bucket([]byte("boltDataBucket")).Cursor()
		for key, value := c.First(); key != nil; key, value = c.Next() {
			_, err := f.Write(goettybuf.Int2Bytes(len(key)))
			if err != nil {
				return err
			}

			_, err = f.Write(goettybuf.Int2Bytes(len(value)))
			if err != nil {
				return err
			}

			_, err = f.Write(key)
			if err != nil {
				return err
			}

			_, err = f.Write(value)
			if err != nil {
				return err
			}
		}
		return nil
	})
}

func (e *boltDataEngine) ApplySnapshot(path string) error {
	data, err := ioutil.ReadFile(fmt.Sprintf("%s/data.sst", path))
	if err != nil {
		return err
	}

	if len(data) == 0 {
		return nil
	}

	buf := goettybuf.NewByteBuf(len(data))
	buf.Write(data)

	e.db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("boltDataBucket"))
		for {
			if buf.Readable() > 8 {
				_, v, err := buf.ReadBytes(4)
				if err != nil {
					return err
				}
				keySize := goettybuf.Byte2Int(v)
				_, v, err = buf.ReadBytes(4)
				if err != nil {
					return err
				}
				valueSize := goettybuf.Byte2Int(v)
				_, key, err := buf.ReadBytes(keySize)
				if err != nil {
					return err
				}
				_, value, err := buf.ReadBytes(valueSize)
				if err != nil {
					return err
				}
				b.Put(key, value)
			} else {
				break
			}
		}
		return nil
	})

	buf.Release()
	return nil
}
